/*
 * acooly.cn Inc.
 * Copyright (c) 2016 All Rights Reserved.
 * create by zhike
 * date:2016年4月4日
 *
 */
package com.acooly.module.openapi.client.provider.baofup.marshall;

import com.acooly.core.common.exception.BusinessException;
import com.acooly.core.utils.Reflections;
import com.acooly.core.utils.Strings;
import com.acooly.core.utils.security.RSA;
import com.acooly.core.utils.validate.Validators;
import com.acooly.module.openapi.client.api.exception.ApiClientException;
import com.acooly.module.openapi.client.provider.baofup.BaoFuPConstants;
import com.acooly.module.openapi.client.provider.baofup.OpenAPIClientBaoFuPProperties;
import com.acooly.module.openapi.client.provider.baofup.domain.BaoFuPApiMessage;
import com.acooly.module.openapi.client.provider.baofup.domain.BaoFuPRequest;
import com.acooly.module.openapi.client.provider.baofup.support.BaoFuPAlias;
import com.acooly.module.openapi.client.provider.baofup.utils.BaoFuPSecurityUtil;
import com.acooly.module.openapi.client.provider.baofup.utils.JsonMarshallor;
import com.acooly.module.openapi.client.provider.baofup.utils.StringHelper;
import com.acooly.module.safety.key.KeyLoadManager;
import com.acooly.module.safety.signature.SignerFactory;
import com.acooly.module.safety.support.CodecEnum;
import com.acooly.module.safety.support.KeyStoreInfo;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.*;
import java.util.*;

/**
 * @author zhike
 */
@Slf4j
public class BaoFuPMarshallSupport {

    @Autowired
    protected OpenAPIClientBaoFuPProperties openAPIClientBaoFuPProperties;

    @Autowired
    protected SignerFactory signerFactory;

    @Autowired
    private KeyLoadManager keyStoreLoadManager;

    private volatile KeyStoreInfo keyStoreInfo;

    private static JsonMarshallor jsonMarshallor = JsonMarshallor.INSTANCE;

    protected OpenAPIClientBaoFuPProperties getProperties() {
        return openAPIClientBaoFuPProperties;
    }

    public SignerFactory getSignerFactory() {
        return signerFactory;
    }

    protected SortedMap<String, String> doMarshall(BaoFuPRequest source) {
        try {
            doVerifyParam(source);
            SortedMap<String, String> requestDataMap = getSignDataMap(source);
            TreeMap<String, String> signParams = Maps.newTreeMap();
            signParams.putAll(requestDataMap);
            if(requestDataMap.containsKey(BaoFuPConstants.DGTL_ENVLP)) {
                //信封加密
                String dgtlEnvlp = signParams.get(BaoFuPConstants.DGTL_ENVLP);
                dgtlEnvlp = doEncodeMessage(BaoFuPSecurityUtil.Base64Encode(dgtlEnvlp), source.getPartner());
                signParams.put(BaoFuPConstants.DGTL_ENVLP, dgtlEnvlp);
                requestDataMap.put(BaoFuPConstants.DGTL_ENVLP, dgtlEnvlp);
            }
            //签名
            String signStr = getWaitToSigin(signParams);
            log.info("请求报文SHA-1摘要字符串：{}", signStr);
            String signature = BaoFuPSecurityUtil.sha1X16(signStr, "UTF-8");
            log.info("请求报文SHA-1摘要结果：" + signature);
            requestDataMap.put(BaoFuPConstants.SIGNER_TYPE,doSign(signature, source));
            return requestDataMap;
        }catch (Exception e) {
            log.info("组装请求报文失败：{}",e.getMessage());
            throw  new BusinessException("组装请求报文失败："+e.getMessage());
        }
    }

    /**
     * 获取代签map
     *
     * @param source
     * @return
     */
    protected SortedMap<String, String> getSignDataMap(BaoFuPRequest source) {
        SortedMap<String, String> signData = Maps.newTreeMap();
        Set<Field> fields = Reflections.getFields(source.getClass());
        String key = null;
        Object value = null;
        for (Field field : fields) {
            value = Reflections.getFieldValue(source, field.getName());
            if (value == null) {
                continue;
            }
            BaoFuPAlias baofuAlias = field.getAnnotation(BaoFuPAlias.class);
            if (baofuAlias == null) {
                continue;
            } else {
                if(baofuAlias.isEncrypt()) {
                    value = BaoFuPSecurityUtil.AesEncrypt(BaoFuPSecurityUtil.Base64Encode((String)value), openAPIClientBaoFuPProperties.getAesKey());//先BASE64后进行AES加密
                }
                if (!baofuAlias.sign()) {
                    continue;
                }
                key = baofuAlias.value();
            }
            if (Strings.isNotBlank((String) value)) {
                signData.put(key, (String) value);
            }
        }
        return signData;
    }

    protected String getWaitToSigin(TreeMap<String, String> plain) {
        String waitToSignStr = null;
        if (plain.containsKey(BaoFuPConstants.SIGNER_TYPE)) {
            plain.remove(BaoFuPConstants.SIGNER_TYPE);
        }

        StringBuilder stringToSign = new StringBuilder();
        if (plain.size() > 0) {
            Iterator var5 = plain.entrySet().iterator();

            while (var5.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry) var5.next();
                if (entry.getValue() != null) {
                    stringToSign.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
                }
            }

            stringToSign.deleteCharAt(stringToSign.length() - 1);
            waitToSignStr = stringToSign.toString();
        }

        return waitToSignStr;
    }

    /**
     * 校验参数
     *
     * @param source
     */
    protected void doVerifyParam(BaoFuPApiMessage source) {
        try {
            source.doCheck();
            Validators.assertJSR303(source);
        } catch (Exception e) {
            throw new ApiClientException(e.getMessage());
        }
    }

    /**
     * 签名
     * @param waitForSign
     * @param source
     * @return
     */
    protected String doSign(String waitForSign, BaoFuPRequest source) {
        try {
            KeyStoreInfo key = getKeyInfo(source.getPartner());
            Signature signature = Signature.getInstance(key.getSignatureAlgo());
            signature.initSign(key.getPrivateKey());
            signature.update(waitForSign.getBytes(key.getPlainEncode()));
            return StringHelper.byte2Hex(signature.sign());
        }catch (Exception e) {
            log.info("签名失败：{}",e.getMessage());
            throw new BusinessException("签名失败：{}",e.getMessage());
        }
    }

    /**
     * 验签
     * @param signatureStr
     * @param plain
     * @param partnerId
     */
    protected void doVerify(String signatureStr, String plain, String partnerId) {
        try {
            KeyStoreInfo key = getKeyInfo(partnerId);
            PublicKey publicK = key.getCertificate().getPublicKey();
            Signature signature = Signature.getInstance(key.getSignatureAlgo());
            signature.initVerify(publicK);
            signature.update(plain.getBytes(key.getPlainEncode()));
            boolean result = signature.verify(StringHelper.hex2Bytes(signatureStr));
            if(!result) {
                log.info("验证签名失败");
                throw new BusinessException("验证签名失败");
            }
        }catch (Exception e) {
            log.info("验证签名失败：{}",e.getMessage());
            throw new BusinessException("验证签名失败："+e.getMessage());
        }
    }
    /**
     * 根据私钥解密
     *
     * @param encodeDataContent
     * @return
     */
    protected String doDecodeByProvateKey(String encodeDataContent,String partnerId) {
        KeyStoreInfo key = getKeyInfo(partnerId);
        PrivateKey privateKey = key.getPrivateKey();
        String encodeStr= BaoFuPSecurityUtil.Base64Decode(decryptByPrivateKey(encodeDataContent,privateKey));
        return encodeStr;
    }


    /**
     * 根据公钥加密
     *
     * @param encodeDataContent
     * @return
     */
    protected String doEncodeMessage(String encodeDataContent,String partnerId) {
        KeyStoreInfo key = getKeyInfo(partnerId);
        PublicKey publicKey = key.getCertificate().getPublicKey();
        return encryptByPublicKey(encodeDataContent,publicKey);
    }

    /**
     * 公钥加密返回
     *
     * @param src
     * @param publicKey
     * @return hex串
     */
    public String encryptByPublicKey(String src, PublicKey publicKey) {
        try {
            byte[] destBytes = rsaByPublicKey(src.getBytes(), publicKey, Cipher.ENCRYPT_MODE);

            if (destBytes == null) {
                return null;
            }
            return StringHelper.byte2Hex(destBytes);
        }catch (Exception e) {
            log.error("加密内容不是正确的UTF8格式:", e);
            throw new BusinessException("公钥加密失败:"+e.getMessage());
        }

    }
    /**
     * 根据公钥解密
     *
     * @param src
     * @param publicKey
     * @return
     */
    public String decryptByPublicKey(String src, PublicKey publicKey) {

        try {
            byte[] destBytes = rsaByPublicKey(StringHelper.hex2Bytes(src), publicKey, Cipher.DECRYPT_MODE);

            if (destBytes == null) {
                throw new BusinessException("公钥解密失败");
            }
            return new String(destBytes, BaoFuPConstants.ENCODE);
        } catch (UnsupportedEncodingException e) {
            log.error("解密内容不是正确的UTF8格式:", e);
            throw new BusinessException("公钥解密失败:"+e.getMessage());
        }
    }

    /**
     * 私钥解密
     *
     * @param src
     * @param privateKey
     * @return
     */
    public String decryptByPrivateKey(String src, PrivateKey privateKey) {
        if (Strings.isBlank(src)) {
            return null;
        }
        try {
            byte[] destBytes = rsaByPrivateKey(StringHelper.hex2Bytes(src), privateKey, Cipher.DECRYPT_MODE);
            if (destBytes == null) {
                return null;
            }
            return new String(destBytes, "UTF-8");
        } catch (Exception e) {
			log.error("解密内容不是正确的UTF8格式:", e);
            throw new BusinessException("私钥解密失败:"+e.getMessage());
        }
    }

    /**
     * 公钥算法
     *
     * @param srcData   源字节
     * @param publicKey 公钥
     * @param mode      加密 OR 解密
     * @return
     */
    public byte[] rsaByPublicKey(byte[] srcData, PublicKey publicKey, int mode) {
        try {
            Cipher cipher = Cipher.getInstance(BaoFuPConstants.RSA_CHIPER);
            cipher.init(mode, publicKey);
            // 分段加密
            int blockSize = (mode == Cipher.ENCRYPT_MODE) ? BaoFuPConstants.ENCRYPT_KEYSIZE : BaoFuPConstants.DECRYPT_KEYSIZE;
            byte[] encryptedData = null;
            for (int i = 0; i < srcData.length; i += blockSize) {
                // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
                byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(srcData, i, i + blockSize));
                encryptedData = ArrayUtils.addAll(encryptedData, doFinal);
            }
            return encryptedData;
        } catch (NoSuchAlgorithmException e) {
            log.error("公钥算法-不存在的解密算法:", e);
        } catch (NoSuchPaddingException e) {
            log.error("公钥算法-无效的补位算法:", e);
        } catch (IllegalBlockSizeException e) {
            log.error("公钥算法-无效的块大小:", e);
        } catch (BadPaddingException e) {
            log.error("公钥算法-补位算法异常:", e);
        } catch (InvalidKeyException e) {
            log.error("公钥算法-无效的私钥:", e);
        }
        return null;
    }

    /**
     * 私钥算法
     *
     * @param srcData
     *            源字节
     * @param privateKey
     *            私钥
     * @param mode
     *            加密 OR 解密
     * @return
     */
    public byte[] rsaByPrivateKey(byte[] srcData, PrivateKey privateKey, int mode) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(mode, privateKey);
            // 分段加密
            int blockSize = (mode == Cipher.ENCRYPT_MODE) ? cipher.getOutputSize(srcData.length)-11 : cipher.getOutputSize(srcData.length);
            byte[] decryptData = null;

            for (int i = 0; i < srcData.length; i += blockSize) {
                byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(srcData, i, i + blockSize));

                decryptData = ArrayUtils.addAll(decryptData, doFinal);
            }
            return decryptData;
        } catch (NoSuchAlgorithmException e) {
			log.error("私钥算法-不存在的解密算法:", e);
        } catch (NoSuchPaddingException e) {
            log.error("私钥算法-无效的补位算法:", e);
        } catch (IllegalBlockSizeException e) {
            log.error("私钥算法-无效的块大小:", e);
        } catch (BadPaddingException e) {
            log.error("私钥算法-补位算法异常:", e);
        } catch (InvalidKeyException e) {
            log.error("私钥算法-无效的私钥:", e);
        }
        return null;
    }

    /**
     * 获取keyStoreInfo
     *
     * @return
     */
    protected KeyStoreInfo getKeyInfo(String partnerId) {
        try {
            KeyStoreInfo keyStoreInfo = keyStoreLoadManager.load(partnerId, BaoFuPConstants.PROVIDER_NAME);
            log.debug("商户partnerId={}启用密钥托管");
            return keyStoreInfo;
        } catch (Exception e) {
            log.debug("商户partnerId={}启用配置文件密钥", partnerId);
            return getKeyStoreInfo();
        }
    }

    protected void beforeMarshall(BaoFuPApiMessage message) {

    }

    public KeyStoreInfo getKeyStoreInfo() {
        if (null == keyStoreInfo) {
            synchronized (BaoFuPMarshallSupport.class) {
                if (keyStoreInfo == null) {
                    keyStoreInfo = new KeyStoreInfo();
                    keyStoreInfo.setKeyStoreUri(openAPIClientBaoFuPProperties.getPrivateKeyPath());
                    keyStoreInfo.setKeyStorePassword(openAPIClientBaoFuPProperties.getPrivateKeyPassword());
                    keyStoreInfo.setCertificateUri(openAPIClientBaoFuPProperties.getPublicKeyPath());
                    keyStoreInfo.setKeyStoreType(KeyStoreInfo.KEY_STORE_PKCS12);
                    keyStoreInfo.setPlainEncode("utf-8");
                    keyStoreInfo.setSignatureAlgo(RSA.SIGN_ALGO_SHA1);
                    keyStoreInfo.setSignatureCodec(CodecEnum.HEX);

                    // 最后load下，内部会缓存。
                    keyStoreInfo.loadKeys();
                }
            }

        }
        return keyStoreInfo;
    }
}
